package prometheus

import (
	"fmt"
	"net/http"
	"sync"
	"time"
    "log"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const NameSpace = "cbp"

var _default = NewPrometheus()

func Default() *Prometheus { return _default }

func NewPrometheus() *Prometheus {
	return &Prometheus{
		namespace: NameSpace,
		registry:  prometheus.NewRegistry(),
	}
}

type Prometheus struct {
	namespace string
	regist    sync.Map
	registry  *prometheus.Registry
}

//在服务中内嵌一个Prometheus的http服务
func (p *Prometheus) Start(host string) *Prometheus {
	go func() {
		server := http.NewServeMux()
		server.Handle("/metrics", promhttp.InstrumentMetricHandler(p.registry, promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{ErrorLog: &promeLogger{}})))
		log.KV("addr", host).Info("prometheus http start")
		err := http.ListenAndServe(host, server) // start an http server using the mux server
		if err != nil {
			log.KV("err", err).Warn("prometheus http stop")
		}
	}()
	return p
}

//计数器(累加指标)
func (s *Prometheus) RegCounter(subsystem, name string, labels []string) *CounterVec {
	vec := prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: s.namespace, Subsystem: subsystem, Name: name},
		labels,
	)
	return s.register(subsystem, name, &CounterVec{vec}).(*CounterVec)
}

//计量器(当前值指标)
func (s *Prometheus) RegGauge(subsystem, name string, labels []string) *GaugeVec {
	vec := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: s.namespace, Subsystem: subsystem, Name: name},
		labels,
	)
	return s.register(subsystem, name, &GaugeVec{vec}).(*GaugeVec)
}

//分布图(直方图)
func (s *Prometheus) RegHistogram(subsystem, name string, labels []string, buckets []float64) *HistogramVec {
	vec := prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: s.namespace, Subsystem: subsystem, Name: name, Buckets: buckets},
		labels,
	)
	return s.register(subsystem, name, &HistogramVec{vec}).(*HistogramVec)
}

//摘要(概略图)
func (s *Prometheus) RegSummary(subsystem, name string, labels []string, objectives map[float64]float64, maxAge time.Duration, ageBuckets, bufCap uint32) *SummaryVec {
	vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{
		Namespace: s.namespace, Subsystem: subsystem, Name: name, Objectives: objectives, MaxAge: maxAge, AgeBuckets: ageBuckets, BufCap: bufCap},
		labels,
	)
	return s.register(subsystem, name, &SummaryVec{vec}).(*SummaryVec)
}

func (p *Prometheus) register(subsystem, name string, c prometheus.Collector) prometheus.Collector {
	key := fmt.Sprintf("%s/%s/%s", p.namespace, subsystem, name)
	if old, load := p.regist.LoadOrStore(key, c); load {
		//log.KV("key", key).InfoStack(2, "prometheus regist same key")
		return old.(prometheus.Collector)
	}
	p.registry.MustRegister(c)
	return c
}

type promeLogger struct{}

func (pl *promeLogger) Println(v ...interface{}) {
	log.KV("msg", fmt.Sprintln(v...)).Warn("prometheus error happen")
}

type (
	CounterVec   struct{ *prometheus.CounterVec }
	GaugeVec     struct{ *prometheus.GaugeVec }
	HistogramVec struct{ *prometheus.HistogramVec }
	SummaryVec   struct{ *prometheus.SummaryVec }
)

func (s *CounterVec) Inc(lvs ...string) {
	m, err := s.GetMetricWithLabelValues(lvs...)
	if err != nil {
		log.KV("err", err).WarnStack(1, "counter inc failed")
		return
	}
	m.Inc()
}

func (s *CounterVec) Add(val float64, lvs ...string) {
	m, err := s.GetMetricWithLabelValues(lvs...)
	if err != nil {
		log.KV("err", err).WarnStack(1, "counter add failed")
		return
	}
	m.Add(val)
}

func (s *GaugeVec) Set(val float64, lvs ...string) {
	m, err := s.GetMetricWithLabelValues(lvs...)
	if err != nil {
		log.KV("err", err).WarnStack(1, "gauge set failed")
		return
	}
	m.Set(val)
}

func (s *HistogramVec) Observe(val float64, lvs ...string) {
	m, err := s.GetMetricWithLabelValues(lvs...)
	if err != nil {
		log.KV("err", err).WarnStack(1, "histogram observe failed")
		return
	}
	m.Observe(val)
}
